出处:掘金

原作者:ErpanOmer


WeakRefFinalizationRegistry 是 JavaScript 中用于与垃圾回收器(GC)协同工作的两个高级 API,它们不干预 GC 行为,但允许我们在 GC 发生之后获取“副作用”的钩子。本质上,它们的触发并非实时,而是受 GC 策略支配、时间不确定、不可预测的“异步响应机制”

WeakRef:对对象的“弱引用”

let obj = { name: "hello" };
let ref = new WeakRef(obj);

obj = null;

let derefValue = ref.deref();  // 有可能返回对象,也有可能已被 GC 回收后返回 undefined

GC 触发条件:

注意:

FinalizationRegistry:对象死亡之后的清理通知

const registry = new FinalizationRegistry((heldValue) => {
  console.log('对象被回收:', heldValue);
});

let obj = { foo: "bar" };
registry.register(obj, "my-object");

obj = null;  // 此后某个时间,GC 会清理 obj,并异步调用注册函数

GC 触发条件:

特别强调:

实际中的 GC 触发机制(以 V8 为例)

V8 引擎(Chrome 和 Node.js 使用)中的 GC 策略具有多级触发机制:

  1. Minor GC(新生代 GC):对象分配压力大或空间满了
  2. Major GC(老年代 GC):对象长期存活,内存紧张时触发
  3. Idle GC(空闲时 GC):浏览器空闲帧期间补回收
  4. Low memory GC:系统内存吃紧时强制触发

WeakRef 和 FinalizationRegistry 都不影响 GC 是否执行,它们的工作依赖于:

冷知识与陷阱

不能依赖 WeakRef 或 FinalizationRegistry 来做逻辑控制

因为:

错误用法:指望 WeakRef 判断对象是否“还活着”

const ref = new WeakRef(obj);
if (ref.deref()) {
  // 认为 obj 还活着?错!它可能刚好 GC 还没动手!
}

这不是“活着”的判断方法。它只是“此刻 GC 可能还没动手”

正确用途示例:LRU 缓存弱引用条目

const cache = new Map();

function getData(id) {
  const ref = cache.get(id);
  const val = ref?.deref();

  if (val) return val;

  const newVal = loadData(id);
  cache.set(id, new WeakRef(newVal));
  return newVal;
}

当数据被垃圾回收,WeakRef 会自动无效,下一次访问时重新加载

总结表:两者对比

特性 WeakRef FinalizationRegistry
是否影响 GC 否(不会阻止对象被 GC) 否(对象仍会被正常 GC)
GC 触发条件 目标对象无强引用且被 GC 回收 同上
是否保证触发 不保证 不保证
回调是否同步 无回调 异步回调
应用场景 缓存、软引用 清理副资源、日志、释放钩子等
是否能判断对象“还活着”

结语

WeakRef 与 FinalizationRegistry 是为应对现代复杂内存生命周期而生的工具。它们并不是让你掌控垃圾回收,而是允许你“在对象死亡之后做点事”

使用时需牢记两个原则:

  1. 它们无法预测、控制或强制 GC
  2. 它们只是 GC 行为之后的观察器,适合辅助优化而非核心逻辑

结论: